#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>


#define MEM_SIZE 10240
#define TRUE 1
#define FALSE 0

typedef struct sockaddr SA;
typedef struct sockaddr_in SA_IN;


#define HTTP_ACCEPT "Accept: */*\n"
#define HTTP_USER_AGENT "User-Agent: http sock/1.0\n"
#define HTTP_CONTENT_TYPE "Content-Type: application/json\n"
#define HTTP_CONTENT "Connection: close\n"
#define HTTP_HEAD "http://"


static char *strnstr(const char *str,int len)
{
	char *res = NULL;

	if(str == NULL)
	{
		return NULL;
	}

	if(len < 0)
		len=strlen(str)+len;

	res = malloc(len + 1);
	if(res == NULL)
		return NULL;
	
	snprintf(res,len+1,"%s",str);

	return res;
}


static char *read_all(int sockfd)
{
	char *res = NULL;
	int size = MEM_SIZE;
	int n = 0;
	int len=0;
	int errno_count = 0;

	res = malloc(MEM_SIZE);
	if(res == NULL)
		return NULL;

	while(1)
	{
		n = recv(sockfd, res + len, size, 0);
		if(n <= 0)
		{   
			//DEBUG_PRINT("errno %d\n", errno);
			if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
			{
				errno_count++;
				if(errno_count > 100)
				{
					printf("errno_count %d errno %d\n", errno_count, errno);
					break;
				}
				usleep(2*1000);
				continue;
			}
			break;
		}
		
		len += n;
		
		if(n < size)
			size -= n;

		if(len % MEM_SIZE == 0)
		{
			char *temp = NULL;
			
			while((temp = malloc(len)) == NULL);
			strncpy(temp, res, len);
			free(res);
			res = NULL;

			while((res = malloc(len + MEM_SIZE)) == NULL);
			strncpy(res, temp, len);
			free(temp);
			temp = NULL;

			size = MEM_SIZE;
		}
	}

	if(res)
	{
		res[len] = '\0';
	}

	return res;
}

static int TcpSend(int sockfd, const char *buf, int size)
{
	int n = 0;
	int left = 0;
	char *pBuf;
	
	left = size;
	pBuf = (char *)buf;

	int errno_count = 0;

	while(left > 0)
	{
		n = send(sockfd, pBuf, left, MSG_DONTWAIT | MSG_NOSIGNAL);

		if (n <= 0)
		{
			/* EINTR A signal occurred before any data was transmitted. */
			if(errno == EINTR)
			{
				n = 0;
				printf("socket send_n eintr error");
				usleep(1000);
				continue;
			} 
			else if(errno == 11)
			{
				errno_count++;
				if(errno_count >= 100)
				{
					printf("errno_count >= 100\n");
					errno_count = 0;
					return(-1);
				}

				usleep(20000);
				continue;
			}
			else
			{
				printf("socket ret %d errno = %d\n", n, errno);
				return(-1);
			}
		}
		
		errno_count = 0;

		if(n != left)
		{
			printf("%s %d:n != left %d != %d\n", __func__, __LINE__, n, left);
		}
		
		left -= n;
		pBuf += n;

		if(left > 0)
		{
			usleep(3000);
		}	
	}

	return size;
}


static int init_data_with_client(SA_IN *server_addr,
		const char *host,unsigned int port)
{
	struct addrinfo *answer, hint, *curr;
	char ipstr[16];   
	bzero(&hint, sizeof(hint));
	hint.ai_family = AF_INET;
	hint.ai_socktype = SOCK_STREAM;

	int ret = getaddrinfo(host, NULL, &hint, &answer);
	if (ret != 0) 
	{
		fprintf(stderr,"getaddrinfo: %s\n", gai_strerror(ret));
		return -1;
	}

	for (curr = answer; curr != NULL; curr = curr->ai_next) 
	{
		inet_ntop(AF_INET, &(((struct sockaddr_in *)(curr->ai_addr))->sin_addr), ipstr, 16);
		bzero(server_addr,sizeof(SA_IN));
		server_addr->sin_family=AF_INET;
		server_addr->sin_port=htons(port);
		server_addr->sin_addr.s_addr=inet_addr(ipstr);
		//DEBUG_PRINT("%s\n", ipstr);
		break;
	}

	freeaddrinfo(answer);

	return 0;
}


static int tcp_connect(const char *url,unsigned int port)
{
	SA_IN server_addr;
	int sockfd;
	int ret = 0;
	struct timeval tv = {3, 0};

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		return sockfd;
	}

	setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
  	setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
	
	ret = init_data_with_client(&server_addr, url, port);
	if(ret < 0)
	{
		printf("init_data_with_client fail, ret %d\n", ret);
		close(sockfd);
		return -1; 
	}

	int flags = fcntl(sockfd, F_GETFL, 0); 
	fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); 
	ret = connect(sockfd, (SA *)&server_addr, sizeof(SA_IN));
	if(ret < 0)
	{
		if(errno != EINPROGRESS && errno != EWOULDBLOCK) 
		{
			perror("connect fail");
		}
        		
		fd_set writefds;   
		struct timeval 	tv;

		tv.tv_sec = 5; 
		tv.tv_usec= 0;
		FD_ZERO(&writefds);
		FD_SET(sockfd, &writefds);

		ret = select(sockfd + 1, NULL, &writefds, NULL, &tv);
		
		if(ret <= 0)
		{
			perror("connect fail");
			close(sockfd);
			return -1;
		}
	}
	fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
	return sockfd;
}


char *client_http_post(const char *url, unsigned int port, const char *data, char *cookie)
{
	int sockfd = 0;
	char content_length[64] = {0};
	char host[64] = {0};
	char head[128] = {0};
	char ipaddr[32] = {0};
	char cookie_data[128] = {0};
	char *temp = NULL;
	char *res = NULL;
	int host_len=0;
	int n=0;
	int i = 0;
	
	if(NULL == url || NULL == data || NULL == cookie)
	{
		printf("param is NULL\n");
		return NULL;
	}

	if(strstr(url, HTTP_HEAD))
	{
		n = strlen(HTTP_HEAD);
	}
	else
	{
		printf("url %s is not right\n", url);
		return NULL;	
	}
	
	for(i = n; url[i]; ++i, ++host_len)
		if(url[i] == '/')
			break;
		
	snprintf(content_length, sizeof(content_length), "Content-Length: %ld\n\n", strlen(data));
	snprintf(head, sizeof(head), "POST %s HTTP/1.1\n", url+n+host_len);
	temp = strnstr(url + n, host_len);
	if(temp)
	{
		snprintf(host, sizeof(host), "Host: %s\n", temp);
		snprintf(ipaddr, sizeof(ipaddr), "%s", temp);
		free(temp);
	}
	snprintf(cookie_data, sizeof(cookie_data), "Cookie: %s;\n", cookie);

	if((sockfd = tcp_connect(ipaddr, port)) == -1)
	{	
		printf("tcp_connect fail\n");
		return NULL;
	}
	
	TcpSend(sockfd, head, strlen(head));
	TcpSend(sockfd, host, strlen(host));
	TcpSend(sockfd, HTTP_ACCEPT, strlen(HTTP_ACCEPT));
	TcpSend(sockfd, HTTP_USER_AGENT, strlen(HTTP_USER_AGENT));
	TcpSend(sockfd, HTTP_CONTENT_TYPE, strlen(HTTP_CONTENT_TYPE));
	TcpSend(sockfd, HTTP_CONTENT, strlen(HTTP_CONTENT));
	TcpSend(sockfd, cookie_data, strlen(cookie_data));
	TcpSend(sockfd, content_length, strlen(content_length));
	
	TcpSend(sockfd, data, strlen(data));

	res = read_all(sockfd);
	close(sockfd);

	return res;
}
